home *** CD-ROM | disk | FTP | other *** search
/ Technotools / Technotools (Chestnut CD-ROM)(1993).ISO / lang_c / ctags3a / ctags.c next >
C/C++ Source or Header  |  1990-08-18  |  10KB  |  388 lines

  1. /* ctags.c -- Generate tags for subsequent use by Brief or VI editors.
  2.  
  3.    Usage: ctags source1.c source2.c ... >tags
  4.  
  5.    This program will perform a simple parsing of one or more C source
  6.    files and write a "tags" file to stdout. This file is then used in
  7.    conjunction with tagging commands build into VI and available (as 
  8.    macros) from the Solution Systems BBS. The tags file will contain
  9.    a line for each procedure in the source file. Each line has the form:
  10.  
  11.    <procedure name> <file name> <search criteria>
  12.  
  13.    The search criteria contains the entire source line containing the
  14.    procedure name to reduce the possibility of the search finding the
  15.    wrong line.
  16.  
  17.    This has been compiled under Microsoft C V4.0 and will also compile
  18.    under Unix V on an AT&T 3B1.    When using Microsoft C, link with
  19.    ssetargv.obj to enable wild card expansion of command line arguments.
  20.  
  21.    01/11/87    Initial release
  22.  
  23.    02/01/87    Misc. minor enhansements.
  24.  
  25.    04/22/87    Tag #defined names in addition to function names.
  26.  
  27.    Paul Verket */
  28.  
  29. /* 05/14/90 Michael Denio -- Modified to put the full path of the filename in
  30.                              the tag file */
  31.  
  32. /* 08/18/90 Morris Maynard - Added cases to handle MSC double slash comments */
  33.  
  34. #define    LINT_ARGS
  35.  
  36. #include <stdio.h>
  37. #include <ctype.h>
  38. #include <string.h>
  39. #include <process.h> /* for "exit" function definition */
  40. #include <direct.h>  /* for "getcwd" function definition */
  41.  
  42. #ifndef SEEK_SET
  43. #    define SEEK_SET    0
  44. #endif
  45.  
  46. typedef enum {T_WORD, T_BRACEEXP, T_COMMA, T_SEMI, T_PREPROCESS, 
  47.     T_OPENPAREN, T_CLOSEPAREN, T_OPENBRACE, T_CLOSEBRACE, T_NL, 
  48.     T_EOF} TOKEN;
  49.  
  50. void find_functions(char * filename, FILE * in_file);
  51.  
  52. void main(int argc, char *argv[])
  53. {
  54.     FILE    *in_file;
  55.  
  56.     if (argc < 2) {
  57.         fprintf(stderr, "Usage: %s file [file...] [>tags]\n", argv[0]);
  58.         exit(1);
  59.         }
  60.  
  61.     /* Cycle through each source-file argument on the command line. */
  62.     while (argc-- > 1) {
  63.         in_file = fopen((++argv)[0], "r");
  64.         if (in_file == NULL) {
  65.             perror(argv[0]);
  66.             exit(1);
  67.             }
  68.         find_functions(argv[0], in_file);
  69.         fclose(in_file);
  70.         }
  71.  
  72.     exit(0);
  73.     }
  74.  
  75. void find_functions(filename, in_file)
  76. char    filename[];
  77. FILE    *in_file;
  78. {
  79.     TOKEN    gettoken(char *, long *, FILE*),
  80.         curr_token;
  81.   void print_defn_line(FILE *, long);
  82.  
  83.     enum    {NEUTRAL, NAME, FN_NAME, INPAREN, INBRACE, CHECK_DEFINE, 
  84.         RECORD_DEFINE, PREPROCESSOR} 
  85.             state = NEUTRAL;
  86.     char    word[132],
  87.             full_path[255],
  88.         function[132];
  89.     long    line_start,
  90.         defn_start;    /* ftell() of the procedure line */
  91.     int    paren_cnt,
  92.         brace_cnt;
  93.  
  94.     getcwd(full_path, sizeof(full_path));
  95.     while ((curr_token = gettoken(word, &line_start, in_file)) != T_EOF) 
  96.         switch((int) state) {
  97.             /* The "home" state. If a "word" is found, assume that it is
  98.            a procedure name. If T_PREPROCESS, look for #define names
  99.            and toss the rest of the line since macro definitions look 
  100.            like procedures. If an open brace is found, start gobbling 
  101.            up the text contained within the braces. Keep a brace count 
  102.            to handle nested braces. */
  103.         case NEUTRAL:
  104.             switch ((int) curr_token) {
  105.                 case T_WORD:
  106.                     state = NAME;
  107.                     /* Note that the parens may start on
  108.                        the next line, so store the offset
  109.                        now. */
  110.                     defn_start = line_start;
  111.                     continue;
  112.                 case T_PREPROCESS:
  113.                     state = CHECK_DEFINE;
  114.                     defn_start = line_start;
  115.                     continue;
  116.                 case T_OPENBRACE:
  117.                     state = INBRACE;
  118.                     brace_cnt = 1;
  119.                     continue;
  120.                 default:
  121.                     continue;
  122.                 }
  123.         /* All subsequent "word"s will be assumed to be the real
  124.            function name until an open paren is found. If something
  125.            other than a word or paren is found, then this wasn't
  126.            a function name after all. */
  127.         case NAME:
  128.             switch ((int) curr_token) {
  129.                 case T_WORD:
  130.                 case T_NL:
  131.                     defn_start = line_start;
  132.                     continue;
  133.                 case T_OPENPAREN:
  134.                     state = INPAREN;
  135.                     strcpy(function, word);
  136.                     paren_cnt = 1;
  137.                     continue;
  138.                 default:
  139.                     state = NEUTRAL;
  140.                     continue;
  141.                 }
  142.         /* Eat up all the stuff within parens until the close paren
  143.            is found. Keep a counter to handle nested parens. */
  144.         case INPAREN:
  145.             switch ((int) curr_token) {
  146.                 case T_OPENPAREN:
  147.                     paren_cnt++;
  148.                     continue;
  149.                 case T_CLOSEPAREN:
  150.                     if (--paren_cnt == 0)
  151.                         state = FN_NAME;
  152.                     continue;
  153.                 default:
  154.                     continue;
  155.                 }
  156.         /* If a comma or a semicolon is found, then this was a false
  157.            alarm. If an opening brace or another word is found, then
  158.            we found a procedure definition. */
  159.         case FN_NAME:
  160.             switch ((int) curr_token) {
  161.                 case T_COMMA:
  162.                 case T_SEMI:
  163.                     state = NEUTRAL;
  164.                     continue;
  165.                 case T_NL:
  166.                     continue;
  167.                 case T_OPENBRACE:
  168.                     state = INBRACE;
  169.                     brace_cnt = 1;
  170.                     printf("%s %s\\%s ", function, full_path, filename);
  171.                     print_defn_line(in_file, defn_start);
  172.                     continue;
  173.                 default:
  174.                     state = NEUTRAL;
  175.                     printf("%s %s\\%s ", function, full_path, filename);
  176.                     print_defn_line(in_file, defn_start);
  177.                     continue;
  178.                 }
  179.         /* Loop until the closing brace is found. Keep a counter to
  180.            handle nested braces. */
  181.         case INBRACE:
  182.             switch((int) curr_token) {
  183.                 case T_OPENBRACE:
  184.                     brace_cnt++;
  185.                     continue;
  186.                 case T_CLOSEBRACE:
  187.                     if (--brace_cnt == 0) 
  188.                         state = NEUTRAL;
  189.                     continue;
  190.                 default:
  191.                     continue;
  192.                 }
  193.         /* Check preprocessor lines for #define statements */
  194.         case CHECK_DEFINE:
  195.             switch ((int) curr_token) {
  196.                 case T_WORD:
  197.                     if (0 == strcmp(word, "define"))
  198.                         state = RECORD_DEFINE;
  199.                     else state = PREPROCESSOR;
  200.                     continue;
  201.                 default:
  202.                     state = PREPROCESSOR;
  203.                     continue;
  204.                 }
  205.         /* Record the defined name in the same way as function names */
  206.         case RECORD_DEFINE:
  207.             state = PREPROCESSOR; /* toss the rest */
  208.             printf("%s %s\\%s ", word, full_path, filename);
  209.             print_defn_line(in_file, defn_start);
  210.             continue;
  211.         /* Handle the preprocessor line until a new-line is found.
  212.            The tokenizer tosses escaped new lines. */
  213.         case PREPROCESSOR:
  214.             switch ((int) curr_token) {
  215.                 case T_NL:
  216.                     state = NEUTRAL;
  217.                     continue;
  218.                 default:
  219.                     continue;
  220.                 }
  221.         }
  222.     }
  223.  
  224. /* Break up input file into tokens. Take care with characters inside quotes
  225.    and comments that might cause trouble. (like braces and parens!) */
  226. static TOKEN gettoken(word, line_start, in_file)
  227. char    *word;
  228. long    *line_start;
  229. FILE    *in_file;
  230. {
  231.     enum {NEUTRAL, INQUOTE, INSQUOTE, INWORD, INCOMMENT, INDSLASH} 
  232.         state = NEUTRAL;
  233.     static int    col_count = 0;
  234.     int    c,
  235.         c2;
  236.     char    *w;
  237.  
  238.     w = word;
  239.     while ((c = getc(in_file)) != EOF) {
  240.         /* Keep a column count to aid in finding preprocessor lines.
  241.            Keep the ftell() of the start of the line for use when a
  242.            source line is to be printed. */
  243.         if (c == '\n') {
  244.             col_count = 0;
  245.             *line_start = ftell(in_file);
  246.             }
  247.         else col_count++;
  248.  
  249.         switch((int) state) {
  250.             /* The "home" state. Quoted strings and comments are
  251.                stripped. Words consisting of letters, digits and
  252.                the underscore are gathered. */
  253.             case NEUTRAL:
  254.                 switch(c) {
  255.                     case '(':
  256.                         return T_OPENPAREN;
  257.                     case ')':
  258.                         return T_CLOSEPAREN;
  259.                     case '#':
  260.                         if (col_count == 1)
  261.                             return T_PREPROCESS;
  262.                         continue;
  263.                     case '\n':
  264.                         return T_NL;
  265.                     case '"':
  266.                         state = INQUOTE;
  267.                         continue;
  268.                     case '\'':
  269.                         state = INSQUOTE;
  270.                         continue;
  271.                     case '{':
  272.                         return T_OPENBRACE;
  273.                     case '}':    /*}*/
  274.                         return T_CLOSEBRACE;
  275.                     case '/':    /* start of comment? */
  276.                         if ((c2 = getc(in_file)) == '*')
  277.               {
  278.                             state = INCOMMENT;
  279.                             col_count++;
  280.                             continue;
  281.                             }
  282.             else if (c2 = '/') /* double slash comment */
  283.               {
  284.               state = INDSLASH;
  285.               col_count++;
  286.               continue;
  287.               }
  288.                         else {
  289.                             ungetc(c2, in_file);
  290.                             continue;
  291.                             }
  292.                     case ';':
  293.                         return T_SEMI;
  294.                     case ',':
  295.                         return T_COMMA;
  296.                     case '\\': /* toss the escape */
  297.                         getc(in_file);
  298.                         continue;
  299.                     default:
  300.                         if (isalnum(c) || c == '_') {
  301.                             state = INWORD;
  302.                             *w++ = c;
  303.                             }
  304.                         continue;
  305.                     }
  306.             /* Stay in this state, tossing characters, until the
  307.                closing marker. */
  308.             case INCOMMENT:
  309.                 switch(c) {
  310.                     case '*':    /* end of comment? */
  311.                         if ((c2 = getc(in_file)) == '/') {
  312.                             state = NEUTRAL;
  313.                             col_count++;
  314.                             continue;
  315.                             }
  316.                         else {
  317.                             ungetc(c2, in_file);
  318.                             continue;
  319.                             }
  320.                     default:
  321.                         continue;
  322.                     }
  323.             case INDSLASH:
  324.                 switch(c) {
  325.                     case '\n':    /* end of comment? */
  326.                         state = NEUTRAL;
  327.                         col_count++;
  328.                     default:
  329.                         continue;
  330.                     }
  331.             case INQUOTE:
  332.                 switch(c) {
  333.                     case '"':
  334.                         state = NEUTRAL;
  335.                         continue;
  336.                     case '\\': /* toss the escape */
  337.                         getc(in_file);
  338.                         continue;
  339.                     default:
  340.                         continue;
  341.                     }
  342.             case INSQUOTE:
  343.                 switch(c) {
  344.                     case '\'':
  345.                         state = NEUTRAL;
  346.                         continue;
  347.                     case '\\': /* toss the escape */
  348.                         getc(in_file);
  349.                         continue;
  350.                     default:
  351.                         continue;
  352.                     }
  353.             /* Gather up the word. */
  354.             case INWORD:
  355.                 if (isalnum(c) || c == '_') {
  356.                     *w++ = c;
  357.                     continue;
  358.                     }
  359.                 else    {
  360.                     ungetc(c, in_file);
  361.                     *w = NULL;
  362.                     col_count--;
  363.                     return T_WORD;
  364.                     }
  365.             }
  366.         }
  367.     
  368.     return T_EOF;
  369.     }
  370.  
  371. /* Use the previously stored ftell() of the start of line to dump the source
  372.    line. */
  373. static void print_defn_line(FILE * in_file, long line_start)
  374. {
  375.     long    current_position;
  376.     int    c;
  377.  
  378.     current_position = ftell(in_file);
  379.     fseek(in_file, line_start, SEEK_SET);
  380.  
  381.     printf("?^");
  382.     while ((c = getc(in_file)) != EOF && c != '\n') putchar(c);
  383.     printf("$?\n");
  384.  
  385.     fseek(in_file, current_position, SEEK_SET);
  386.     }
  387.  
  388.